关系表达系:表达一些比较简单的关系;
逻辑表达式:表达一些比较复杂的关系;
条件表达式:"?:"表达式,if else语句的简单表达;
表达式是由运算符、操作数(常量、变量、函数等)和括号按照一定的规则组成的式子;可以将常量、变量和函数认为是最简单的表达式。表达式可以嵌套,每个表达式都有一个值。在计算时要考虑运算符的优先级、结合性及数据类型的转换;计算机中的表达式都写在一行中。表达式有算术、赋值、关系、逻辑、条件和逗号等。在表达式的后边加个分号就是表达式语句。除了控制语句外,几乎都是表达式语句。
要理解由多个操作符组成的表达式,必须先理解操作符的优先级、结合性和操作数的求值顺序。
逗号表达式是一组由逗号分隔的表达式,逗号表达式从左向右计算。逗号表达式的结果是其最右边表达式的值。如果最右边的操作数是左值,则逗号表达式的值也是左值。此类表达式通常用于for循环:
结合性规定了具有相同优先级的操作符如何分组。我们已经遇到过涉及结合性的例子。其中之一使用了赋值操作的右结合性,这个特性允许将多个赋值操作串接起来:
ival = jval = kval = lval // right associative,starting from right
(ival = (jval = (kval = lval))) // equivalent, parenthesized version
该表达式首先将 lval 赋给 kval ,然后将 kval 的值赋给 jval ,最后将 jval 的值再赋给 ival。
另一方面,算术操作符为左结合。表达式
ival * jval / kval * lval // left associative
(((ival * jval) / kval) * lval) // equivalent, parenthesized version
先对 ival 和 jval 做乘法操作,然后乘积除以 kval,最后再将其商与 lval 相乘。
一元操作符和含等号=(赋值操作)的运算符大都是左结合操作符;
短路求值:
int i=6;
int j=1;
if(i>0 || (j++)>0),j++的计算会被忽略,对于||,如果一个为真,其他不需要考虑了,整个表达式是真,适合将可能性小的表达式放在左边先计算;
if(i<0 && (j++)>0),j++的计算会被忽略,对于&&,如果一个为假,其他不需要考虑了,整个表达式是假,适合将可能性大的表达式放在左边先计算;
整型与整型数运算,结果为整型,如 5 / 2 = 2 整型与浮点数运算,结果为浮点数,如5 / 2.0 = 2.5 字符与整型数运算,结果为整型。 字符与浮点数运算,结果为浮点数。 浮点数与浮点数运算,结果为浮点数。
构成逻辑表达式的运算符有关系运算符(&&、||、!)和逻辑运算符(>、>=、<、<=、==、!=等),单独的关系运算符构成的关系表达式是简单的逻辑表达式,逻辑运算符用于连接关系表达式,可以构成复杂的逻辑表达式。C语言逻辑运算的结果是:真与假(1与0)。
空语句
while((*pdst++=*psrc++)!='\0')
;
运算符的结合性与优先级:如自增自减、赋值运算符,是从右到左;算术运算符、关系运算符,是从左到右。
C语言的数据类型是指对数据按某种规则所进行的分类。运算符是说明特定操作的符号,是构造表达式的工具。
表达式计算:根据运算符在表达式中的位置,一般有三种表示方法:中序法(我们日常一般的写法),前序法,后序法(运算符在操作数和前面)。对于中序法因为运算符的优先级问题,计算机在处理上较为复杂,所以一般会将中序法转化为前序或后序法。后序法只需要一个堆栈缓存器(前序法需要两个)。
转换的方式有:括号转换法(先用括号把中序法表达式的运算符优先级分出来,再进行运算符的移动。)、堆栈法;
逗号表达式,从左往右计算,结果是最右边表达式的值,如:
y=(x=2,x+=8,x+12),x+x;
结果是:x=10, y = 20
前增量操作符是“先增后赋”,先自增,后将操作数本身返回;后增量操作符是“先赋后增”,即先把操作数的值返回,然后自增。
*p++;相当于*(p++);
左值必须有自己的地址,赋值号右边的表达式的结果被存入左值对应的地址中;
使用关系运算符不能够清晰地描述更为复杂的逻辑关系,因为一个复杂的逻辑通常会包含多个小的逻辑关系。
如果我们的逻辑更为复杂,有多个逻辑条件,则代码中需要多个嵌套的if语句。为了使用计算机语言简化逻辑关系的描述,计算机语言的专家们提出了逻辑运算符的概念。逻辑运算符主要是实现了人类语言中并且、或者等连词在计算机中的表示。使用逻辑运算符可以连接关系表达式以构成复杂的逻辑表达式。C++中共有3种逻辑运算符,分别为“&&”、“||”和“!”。其中“&&”表示逻辑与运算符,也就是“并且”的含义,当两个表达式同时为真,其结果为真,否则为假。“||”表示逻辑或运算符,即“或者”的含义,当两个表达式中有一个表达式为真,结果为真,否则为假。“!”表示逻辑或运算符,即当表达式的值为真,结果为假,当表达式的值为假,其结果为真。表2描述了逻辑运算符的运算方式。
在同一个表达式中,同一优先级的运算符,运算次序由结合性决定。
iret = 3*5 ,iret*4;
var的最终结果为60。因为变量iret的值为15,计算“15*4”即60
实际上赋值运算符(=)的优先级高于逗号运算符(,),因此,先计算“3 * 5”,将其赋值给iret,再计算iret*4。
逗号表达式通常应用于for语句中,在for循环语句中初始化或修改循环变量。
nMaxValue = (nHeight > nWidth) ? nHeight : nWidth;//条件运算符比if控制结构简单。
因为三目元表达式可以根据条件判断返回结果,所以经常被用来简化if语句。
复合语句的结尾处是没有分号的。
求解逻辑表达式时“有解即停”是什么意思?
当需要判断多个条件同时成立或至少有一个成立时,需要使用逻辑运算符&&和||。表达式A && B表示当A与B均为真时,条件为真;表达式A || B表示当A与B至少一个为真时,条件为真。
当求解 A && B时,只要A为假则整个表达式一定为假,此时不需要求解表达式B。而对于表达式 A || B,只要表达式A为真,则整个表达式一定为真,此时不需要求解表达式B。
逗号运算符:将两个表达式连接起来,又称为“顺序求值运算符”,优先级最低;
An expression consists of a combination of operators and operands. (An operand, recall, is what an operator operates on.)
Every expression has a value.
Statements are the primary building blocks of a program. A program is a series of statements with some necessary punctuation. A statement is a complete instruction to the computer. In C, statements are indicated by a semicolon at the end.
Statements and expressions should normally use variables and constants of just one type. if, however, you mix types, C doesn't stop dead in its tracks the way, say, Pascal does. Instead, it uses a set of rules to make type conversions automatically. This can be a convenience, but it can also be a danger, especially if you are mixing types inadvertently.
序列点是一个时间点(在整个表达式全部计算完毕之后或在||、&&、?:或逗号运算符处,或在函数调用之前,此刻尘埃落定,所有的副作用都己确保结束。ANSI/ISOC标准这样描述:
在上一个和下一个序列点之间,一个对象所保存的值至多只能被表达式的计算修改一次。而且前一个值只能用于决定将要保存的值。
第二句话比较费解,它说在一个表达式中如果某个对象需要写入,則在同一表达式中对该对象的访问应该只局限于直接用于计算将要写入的值。这条规則有效地限制了只有能确保在修改之前才访问变a的表达式为非法。例如i=i+1合法,而a[i]=i++則非法。
1 为什么这样的代码: a[i] = i++; 不能工作?
i左边的引用不确定,i++这样的表达式有一个副作用,会修改自身的值。由于i在同一表达式的其它地方被引用,这会导致无定义的结果。无从判断该引用(左边的a[i])是旧值还是新值。尽管在K&R中建议这类表达式的行为不确定,但C标准却强烈声明它是无定义的。
2 使用我的编译器,下面的代码 int i=7; printf("%d\n", i++ * i++);返回 49?不管按什么顺序计算, 难道不该打印出56吗?
尽管后缀自加和后级自减操作符++和--在输出其旧值之后才会执行运算, 但这里 的"之后”常常被误解,没有任何保证确保自增或自减会在输出变变原值之后和对表达式的其它部分进行计算之前立即进行。也不能保证变量的更新会在表达式“完成”(按照ANSI C的术语,在下一个“序列点”之前“之前的某个时刻进行。本例中.编泽器选择使用变量的旧值相乘以后再对二者进行自增运算。
包含多个不确定的副作用的代码的行为总是被认为末定义。(简单而言,“多个不确定副作用”是指在同一个表达式中使用导致同一对象修改两次或修改以后 又被引用的自增、自减和赋值操作符的任何组合。
3 对于代码 int i = 3; i = i++; 不同编译器给出不同的结果, 有的为3, 有的为 4, 哪个是正确的?
因为i++的副作用,导致未定义。
4 这是个巧妙的表达式: a = b = a = b 它不需要临时变量就可 以交换 a 和 b 的值。
试图在序列点之间两次修改变量a,这是无定义的。
5 我可否用括号来强制执行我所需要的计算顺序?
一般来讲,不行,运算符优先级只能赋予表达式计算部分的顺序。如在下面的代码中:
f()+g()+h()
尽管我们知道乘法运算在加法之前,但这并不能说明这三个函数哪个会被首先调用。如果你需要确保子表达式的计算顺序,你可能需要使用明确的临时变量和独立的语句。
6 可是 && 和 || 运算符呢?我看到过类似 while((c = getchar()) !=EOF && c != ’\n’) 的代码 ……
这个运算符在此处有一个特殊的"短路”例外:如果左边的子表达式决定最终结果(即,真对于||和假对于&&)則右边的子表达式不会计算。因此,从左至右的计算可以确保。对逗表达式也是如此。而且,所有这些运算符(包括?:)都会引入一个额外的内部序列点。
7 那么, 对于 a[i] = i++; 我们不知道 a[] 的哪一个分量会被改写,但 i 的确会增加 1, 对吗?
不一定!如果一个表达式和程序变得未定义,则它的所有方面都会变成末定义。
8 ++i和i++有什么区别?
简单而言:++i 在i存储的值上增加一并向使用它的表达式“返回”新的、增加后的值:而i++对i 增加一,但返回原来的是木增加的值,
9 如果我不使用表达式的值, 我应该用 ++i 或 i++ 来自增一个变量吗?
由于这两种格式区别仅在于生成的值,所以在仅使用它们的副作用时,二者完全一样。但是,在C++中,前缀方式却是首选。
10 为什么如下的代码 int a = 100, b = 100; long int c = a * b; 不能工作?
根据C的内部类型转换规則,乘法是用int进行的,而其结果可能在转换为 long型并賦给左边的c之前溢出或被截短。可以使用明确的类型转换,强迫乘法 以long型进行:
11 我需要根据条件把一个复杂的表达式赋值给两个变量中的一 个。可以用下边这样的代码吗? ((condition) ? a : b) = complicated_expression;
表达式的副作用
1、在表达式的求值过程中不但要提取变量的值,还可能改变变量的值。
如:k=m++
2、表达式能产生副作用的原因:引入了具有副作用的操作。
c2 = c1 + 32 ;
如写成:
c2 = c1 + ('a'-'A') ;
至少在可读性方面要比原来强许多,而且这种写法更不容易出错。
序点(sequence points)是指执行序列中的一些点,在这些点上,该点之前所有运算的副效应都应该结束,并且该点之后的副效应还没有发生。
左值指的是表达式的含义是否表示一块内存。例如:
int i = 1;
i = i + 3 ;
这里赋值号左侧的i 表示的是变量i 所占据的那块内存,因此这个i 是左值。而赋值号右侧的i 则表示i 变量的值,这就是所谓的右值。
很多左值都不可能出现在赋值号的左侧。例如:
const int i = 1 ;
这个i 就不可能出现在赋值号的左侧,而且这个i 也不可以被显式地改变,但由于这个i 可以表示变量所占据的内存,因此这个i 也是左值。
再比如:
int a[2];
a 作为数组名,由于可以表示数组所占据的内存空间,因此也是左值。但是a 同样不能出现在赋值运算符左侧,因为C 语言没有定义数组类型的赋值运算(是个指针常量)。
C 语言规定了三字符序列(Trigraph sequences),允许用“??=”、“??(”、“??/”、“??)”、设计中的谬“??'”、“??<”、“??!”、“??>”、“??-”替代表示“#”、“[”、“\”、“]”、
“^”、“{”、“|”、“}”和“~”。
在写代码时,也有类似问题。C 标准同样规定了替代方案,例如,用“<%”表示“{”、
用“%>”表示“}”。
下面程序的输出,可能会让很多人都感到意外:
#include <stdio.h> int main(void) { printf("??=\n"); // # return 0; }
在有些运算中,并不要求运算符两侧的类型一致。比如对于:
int a[1];
来说,表达式*(a+0)中“+”两侧运算对象的数据类型就不同,但这里不存在任何类型转换。
再比如:
unsigned int u=3U;
表达式u<<2 中“<<”两侧运算对象的数据类型也不相同,但这里同样不存在类型转换。C 语言并不要求“<<”运算符两侧的类型一致。
111=2^3-1
2^32-1是int的最大值(unsigned);
C语言中关系运算和逻辑运算结果以1表示真,0表示假,同时规定表达式的值非零即为真。
在条件语句if(a==0) 语句中,当a等于0时条件为真,则执行其后的语句。而在条件语句if(!a) 语句中,当a等于0时,!a即为真,则执行其后执行的语句。因此这两个条件语句是等价的。
注意:类似地,条件语句if(a)等价于if(a!=0)。
若两个操作数都是整数,则为整除操作(求商,舍余),操作结果为整数。
%:只用于整数的求余操作(求余,舍商)。如:10%3=1
goto label;后面有分号。
goto只能在函数内部无条件跳转,不能从一个函数跳转到另外一个函数。
label:的标识在使用goto语句的前后均可,不遵循先定义后使用。
label的作用域在函数内部,不同函数之间可以定义相同的label。
C 语言的语句实际上一共有以下6 种:
标号语句(labeled-statement);
复合语句(compound-statement);
表达式语句(expression-statement);
选择语句(selection-statement);
循环语句(iteration-statement);
跳转语句(jump-statement)。
Logical operators enable you to test for more than one relationship in a while or if condition.
!(x && y) is equivalent to !x || !y
!(x || y) is equivalent to !x && !y
int x=7;
x = x + 1;
In this statement, the variable x is being used in two different contexts. On the left side of the assignment operator, “x” is being used as an l-value (variable with an address). On the right side of the assignment operator, x is being used as an r-value, and will be evaluated to produce a value (in this case, 7). When C++ evaluates the above statement, it evaluates as:
x = 7 + 1;
Note: const variables are considered non-modifiable l-values.